package ke24.ga.extension.adaptation.feedback;
import java.util.List;

import org.jgap.Configuration;
import org.jgap.IChromosome;
import org.jgap.IUniversalRateCalculator;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.jgap.RandomGenerator;
import ke24.ga.Package;
import ke24.ga.LoadDistributionFitnessFunction;
import ke24.ga.extension.*;
public class AdaptationLoadDistributionSwappingMutationOperator extends LoadDistributionSwappingMutationOperator{

    protected Package[] packages;
    protected int invokedCount = 0;
    /**
     * Constructs a new instance of this operator.<p>
     * Attention: The configuration used is the one set with the static method
     * Genotype.setConfiguration.
     *
     * @throws InvalidConfigurationException
     *
     * @author Klaus Meffert
     */
    public AdaptationLoadDistributionSwappingMutationOperator()
        throws InvalidConfigurationException {
      super();
    }

    /**
     * @param a_config the configuration to use
     * @throws InvalidConfigurationException
     *
     * @author Klaus Meffert
     * @since 3.0
     */
    public AdaptationLoadDistributionSwappingMutationOperator(final Configuration a_config)
        throws InvalidConfigurationException {
      super(a_config);
    }

    /**
     * Constructs a new instance of this operator with a specified
     * mutation rate calculator, which results in dynamic mutation being turned
     * on.
     *
     * @param a_config the configuration to use
     * @param a_mutationRateCalculator calculator for dynamic mutation rate
     * computation
     * @throws InvalidConfigurationException
     *
     * @author Klaus Meffert
     * @since 3.0 (previously: without a_config)
     */
    public AdaptationLoadDistributionSwappingMutationOperator(final Configuration a_config,
                                    final IUniversalRateCalculator
                                    a_mutationRateCalculator)
        throws InvalidConfigurationException {
      super(a_config, a_mutationRateCalculator);
    }

    /**
     * Constructs a new instance of this MutationOperator with the given
     * mutation rate.
     *
     * @param a_config the configuration to use
     * @param a_desiredMutationRate desired rate of mutation, expressed as
     * the denominator of the 1 / X fraction. For example, 1000 would result
     * in 1/1000 genes being mutated on average. A mutation rate of zero disables
     * mutation entirely
     * @throws InvalidConfigurationException
     *
     * @author tan
     */
    public AdaptationLoadDistributionSwappingMutationOperator(final Configuration a_config,
                                    final int a_desiredMutationRate)
        throws InvalidConfigurationException {
      super(a_config, a_desiredMutationRate);
    }

    public AdaptationLoadDistributionSwappingMutationOperator(final Configuration a_config,
            final int a_desiredMutationRate, Package[] packages)
throws InvalidConfigurationException {
        super(a_config, a_desiredMutationRate);
        this.packages = packages;
}


    /**
     * @param a_population the population of chromosomes from the current
     * evolution prior to exposure to any genetic operators. Chromosomes in this
     * array should not be modified. Please, notice, that the call in
     * Genotype.evolve() to the implementations of GeneticOperator overgoes this
     * due to performance issues
     * @param a_candidateChromosomes the pool of chromosomes that have been
     * selected for the next evolved population
     *
     * @author Audrius Meskauskas
     * @author Klaus Meffert
     * @since 2.0
     */
    public void operate(final Population a_population,
                        List a_candidateChromosomes) {
      // this was a private variable, now it is local reference.
      final IUniversalRateCalculator m_mutationRateCalc = getMutationRateCalc();
      // If the mutation rate is set to zero and dynamic mutation rate is
      // disabled, then we don't perform any mutation.
      // ----------------------------------------------------------------
      if (getMutationRate() == 0 && m_mutationRateCalc == null) {
        return;
      }
      
      invokedCount++;
      // Determine the mutation rate. If dynamic rate is enabled, then
      // calculate it based upon the number of genes in the chromosome.
      // Otherwise, go with the mutation rate set upon construction.
      // --------------------------------------------------------------
      int currentRate;
      if (m_mutationRateCalc != null) {
        currentRate = m_mutationRateCalc.calculateCurrentRate();
      }
      else {
        currentRate = getMutationRate();
      }
      
      RandomGenerator generator = getConfiguration().getRandomGenerator();
      // It would be inefficient to create copies of each Chromosome just
      // to decide whether to mutate them. Instead, we only make a copy
      // once we've positively decided to perform a mutation.
      // ----------------------------------------------------------------
      int size = a_population.size();
      
     
      
     
      
      
      if (isCheckMutation()) {
          double[] fitnessBefore = new double[size];     
          double[] fitnessAfter = new double[size];
          LoadDistributionFitnessFunction fitnessFunction = new LoadDistributionFitnessFunction(packages);
          
          for (int i = 0; i < size; i++) {
            IChromosome x = a_population.getChromosome(i);        
            
                fitnessBefore[i] = x.getFitnessValue();
            
            // This returns null if not mutated:
            IChromosome xm = operate(x, currentRate, generator);
            if (xm != null) {
              a_candidateChromosomes.add(xm);
              if (isCheckMutation())
                  fitnessAfter[i] = fitnessFunction.evaluate(xm);          
              
            }else {
                fitnessAfter[i] = -1;                      
            }
          }
          int changed = 0;
          int improved = 0;
          for (int ind = 0; ind < size; ind++) {
              System.out.println("\tFitness before: " + fitnessBefore[ind] + " after: " + fitnessAfter[ind]);
              System.out.println("fitness reduced by: " + (fitnessBefore[ind] - fitnessAfter[ind]));
              if (fitnessAfter[ind] != -1 && fitnessAfter[ind] != fitnessBefore[ind]) {
                  changed++;
                  if (fitnessAfter[ind] < fitnessBefore[ind]) improved++;
              }                
          } 
          double successfulRates = (((double)improved)/changed);
          setMutationRate(getUpdateMutationRate(currentRate,successfulRates));
          
          System.out.println("Successful mutation rate  = " + successfulRates + "\t next mutation rate: " + 1.0/getUpdateMutationRate(currentRate,successfulRates));
      }else {         
          
          for (int i = 0; i < size; i++) {
            IChromosome x = a_population.getChromosome(i);        
    
            // This returns null if not mutated:
            IChromosome xm = operate(x, currentRate, generator);
            if (xm != null) {
              a_candidateChromosomes.add(xm);                           
            }
          }        
      }
    }

    private int getUpdateMutationRate(int currentRate, double successfulRates) {
        double c = 0.9;
        //increate mutation rate to search for wider space
        if (successfulRates > 0.2)  return (int) Math.round(currentRate * c);
        //decrease mutation rate to focus on the current chromosome
        if (successfulRates < 0.2) return (int) Math.round(currentRate / c);
        return currentRate;
    }
    
    
    private boolean isCheckMutation() {
        int k = 10;
        return (invokedCount%k)==k-1;
    }
    
}
